#include <OpenCLTemplate.hpp>
#include <time.h>
#include<Timer.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <string>
#include <windows.h>
#include <process.h>
using namespace std;
//notworking
#define SIZEOFPLATFORMID 100
#define HEADERSIZE 6

OpenCLPlatforms::OpenCLPlatforms()
{
	
}
void OpenCLPlatforms::Initialize(void *RecivedBuffer)
{
	PRECISION *recived_buffer;
	int start_matrix_A,end_matrix_A;
	recived_buffer = (PRECISION*)RecivedBuffer;
	SetRowsMatrixA(recived_buffer[0]);
	SetColumsMatrixA(recived_buffer[1]);
	SetRowsMatrixB(recived_buffer[2]);
	SetColumsMatrixB(recived_buffer[3]);
	start_matrix_A = recived_buffer[4];
	end_matrix_A = recived_buffer[5];
	SetMatrixA( recived_buffer+HEADERSIZE + start_matrix_A*colums_matrix_A);
	SetMatrixB(recived_buffer + (rows_matrix_A*colums_matrix_A) + HEADERSIZE);
	SetRowsMatrixA(end_matrix_A - start_matrix_A);
		
}

void ThreadFunctionForPlatforms(void* ptr)
{
	time_t start, end, diff;
	start=clock();
	COpenCLTemplate* obj_ptr;
	obj_ptr = (COpenCLTemplate*)ptr;
	obj_ptr->CompleteRun();
	end=clock();
	diff=end-start;
	cout<<"Time for CPU/GPU:\t"<<diff<<endl;
}

void OpenCLPlatforms::DetectPlatforms()
{	
	SafeCall(clGetPlatformIDs(0, NULL, &num_of_platforms), "Error: Getting Platforms. (clGetPlatformsIDs)");
	
	cl_int status = 0;
	size_t deviceListSize;

	cout << "Detecting platforms..." << endl;
	cout << "Available platforms are: " <<num_of_platforms<< endl;
	
	if(num_of_platforms > 0)
	{
		platform_ID = new cl_platform_id[num_of_platforms];
		SafeCall(clGetPlatformIDs(num_of_platforms, platform_ID, NULL), "Error: Getting Platform Ids. (clGetPlatformsIDs)");
		platform_ID_name = new char*[num_of_platforms];
		for(unsigned int i=0; i < num_of_platforms; ++i)
			platform_ID_name[i] = new char[100];

		for(unsigned int i=0; i < num_of_platforms; ++i)
		{
			SafeCall(clGetPlatformInfo(platform_ID[i], CL_PLATFORM_VENDOR, SIZEOFPLATFORMID , platform_ID_name[i], NULL), "Error: Getting Platform Info.(clGetPlatformInfo)");
			cout << "Platform " << i << " : " << platform_ID_name[i] << endl;
		}
	
	}
	else
		cout<<"Error: Platforms not available"<<endl;

}

void OpenCLPlatforms::SetPlatformIDs()
{
	platform_obj = new COpenCLTemplate[num_of_platforms];
	platform_obj[0].rows_matrix_A = rows_matrix_A/2;
	platform_obj[0].colums_matrix_A = colums_matrix_A;
	platform_obj[0].rows_matrix_B = rows_matrix_B;
	platform_obj[0].colums_matrix_B = colums_matrix_B;
	platform_obj[0].start_thread = 0;
	platform_obj[0].AllocateMemoryCPU();

	platform_obj[1].rows_matrix_A = rows_matrix_A/2;
	platform_obj[1].colums_matrix_A = colums_matrix_A;
	platform_obj[1].rows_matrix_B = rows_matrix_B;
	platform_obj[1].colums_matrix_B = colums_matrix_B;
	platform_obj[1].start_thread = rows_matrix_A/2;
	platform_obj[1].AllocateMemoryCPU();
	
	platform_obj[0].SelectedPlatform = platform_ID_name[1];	
	platform_obj[0].platform = platform_ID[1];
	platform_obj[0].type = CL_DEVICE_TYPE_CPU;

	platform_obj[1].SelectedPlatform = platform_ID_name[0];	
	platform_obj[1].platform = platform_ID[0];
	platform_obj[1].type = CL_DEVICE_TYPE_GPU;

}

int OpenCLPlatforms::AllocateMemoryCPU()
{
	//matrix_A = new PRECISION[rows_matrix_A * colums_matrix_A];
	//matrix_B = new PRECISION[rows_matrix_B * colums_matrix_B];
	MATRIX_C = new PRECISION[rows_matrix_A * colums_matrix_B];
	matrix_B_transpose = new PRECISION[rows_matrix_B * colums_matrix_B];
	return 0;
}

void OpenCLPlatforms::MatrixGeneration()
{
	srand(time(NULL));
	for (unsigned int i=0; i<rows_matrix_A * colums_matrix_A; i++)
	{
		if(i%2==0)
			matrix_A[i] = -(PRECISION)(rand()%5);
		else
			matrix_A[i] = (PRECISION)(rand()%5);
	}
		
	for (unsigned int i=0; i<rows_matrix_B * colums_matrix_B; i++)
	{
		if(i%2==0)
			matrix_B[i] = (PRECISION)(rand()%5);
		else
			matrix_B[i] = -(PRECISION)(rand()%5);
	}
}

void OpenCLPlatforms::SetPlatformMatrices()
{
//	for(unsigned int j = 0; j <num_of_platforms; j++)
	{
		//memcpy(platform_obj[i].matrix_A, matrix_A, sizeof(PRECISION)*rows_matrix_A*colums_matrix_A);
		for(unsigned int i = 0; i <rows_matrix_A/2*colums_matrix_A; i++)
			platform_obj[0].matrix_A[i] =matrix_A[i];
		for(unsigned int k = 0; k <rows_matrix_B*colums_matrix_B; k++)
			platform_obj[0].matrix_B[k] =matrix_B[k];

		for(unsigned int i = 0; i <rows_matrix_A/2*colums_matrix_A; i++)
			//platform_obj[1].matrix_A[i] =matrix_A[i+rows_matrix_A/2*colums_matrix_A];
			platform_obj[1].matrix_A[i] =matrix_A[i];
		for(unsigned int k = 0; k <rows_matrix_B*colums_matrix_B; k++)
			platform_obj[1].matrix_B[k] =matrix_B[k];

	//	for(unsigned int k = 100; k <110; k++)
	//		cout<<platform_obj[0].matrix_B[k]<<"  "<<matrix_B[k]<<endl;
		//memcpy(platform_obj[i].matrix_B, matrix_B, sizeof(PRECISION)*rows_matrix_B*colums_matrix_B);
	}
}


void OpenCLPlatforms::CreateThreads()
{
//num_of_platforms+=1;
	HANDLE* thread_ID_Platforms;
	thread_ID_Platforms = new HANDLE[num_of_platforms];
	for(unsigned int i=0; i < num_of_platforms; i++)
		thread_ID_Platforms[i] = (HANDLE)_beginthread(ThreadFunctionForPlatforms,0,(void*)&platform_obj[i]);

	for(unsigned int i=0; i < num_of_platforms; i++)
		WaitForSingleObject(thread_ID_Platforms[i],INFINITE);

}
void OpenCLPlatforms::CombineMatrixC()
{	
//	memcpy((void*)MATRIX_C, (void*)platform_obj[0].matrix_C, sizeof(int)*rows_matrix_A*colums_matrix_B);
//	memcpy((void*)(MATRIX_C + sizeof(int)*rows_matrix_A*colums_matrix_B), (void*)platform_obj[1].matrix_C, sizeof(int)*rows_matrix_A*colums_matrix_B);
//	MATRIX_C = platform_obj[0].matrix_C;
	for(int i = 0; i <rows_matrix_A/2*colums_matrix_B; i++)
             MATRIX_C[i] = platform_obj[0].matrix_C[i];
	for(int i = rows_matrix_A/2*colums_matrix_B; i <rows_matrix_A*colums_matrix_B; i++)
             //MATRIX_C[i+rows_matrix_A*colums_matrix_B/2] = platform_obj[1].matrix_C[i];
			  MATRIX_C[i]  = platform_obj[1].matrix_C[i];
}

int OpenCLPlatforms::MatrixStore()
{
	ofstream matrix_A_write("matrix_A.txt");
	for (int i = 0; i < rows_matrix_A; i++)
	{
		for (int j = 0; j < colums_matrix_A; j++)
			matrix_A_write << matrix_A[j+i*colums_matrix_A]<< "   ";
		matrix_A_write << endl;
	}
	matrix_A_write.close();
	ofstream matrix_B_write("matrix_B.txt");
	for (int i = 0; i < rows_matrix_B; i++)
	{
		for (int j = 0; j < colums_matrix_B; j++)
			matrix_B_write << matrix_B[i*colums_matrix_B+j]<< "   ";
		matrix_B_write << endl;
	}
	matrix_B_write.close();
	ofstream matrix_C_write("matrix_C.txt");
	for (int i = 0; i < rows_matrix_A; i++)
	{
		for (int j = 0; j < colums_matrix_B; j++)
			matrix_C_write <<MATRIX_C[i*colums_matrix_B+j]<< "   ";
		matrix_C_write << endl;
	}
	matrix_C_write.close();
	return 0;
}

void* OpenCLPlatforms::MemoryCopy (void *destination_ptr, const void *source_ptr, int int_size)
{
    char *destination_buffer = (char*)destination_ptr;
    char *Data = (char*)source_ptr;
    for(int i = 0; i <int_size; i++)
            destination_buffer[i] = Data[i];
    return destination_buffer;
}

COpenCLTemplate::COpenCLTemplate()
{
/*	
	matrix_A = new PRECISION[rows_matrix_A * colums_matrix_A];
	matrix_B = new PRECISION[rows_matrix_B * colums_matrix_B];
	matrix_C = new PRECISION[rows_matrix_A * colums_matrix_B];
	matrix_B_transpose = new PRECISION[rows_matrix_B * colums_matrix_B];
*/
}
// Allocate memory for data arrays.
int COpenCLTemplate::MatrixTranspose()
{
	for (int i=0; i<colums_matrix_B; i++)
		for (int j=0; j<rows_matrix_B; j++)
			matrix_B_transpose[i*rows_matrix_B+j] = matrix_B[j*colums_matrix_B+i];
	return 0;
}

// Allocate memory for data arrays.
int COpenCLTemplate::AllocateMemoryCPU()
{
	matrix_A = new PRECISION[rows_matrix_A * colums_matrix_A];
	matrix_B = new PRECISION[rows_matrix_B * colums_matrix_B];
	matrix_C = new PRECISION[rows_matrix_A * colums_matrix_B];
	matrix_B_transpose = new PRECISION[rows_matrix_B * colums_matrix_B];
	return 0;
}
// Initialise CPU data.
int COpenCLTemplate::InitialiseCPU()
{
	return 0;
}

int COpenCLTemplate::InitialiseCL()
{
	cl_int status = 0;
	size_t deviceListSize;


	cl_uint numPlatforms;
	SafeCall(clGetPlatformIDs(0, NULL, &numPlatforms), "Error: Getting Platforms. (clGetPlatformsIDs)");

	if(NULL == platform)
	{
		cout << "Selected platform not found so Exiting Application." << endl;
		return 1;
	}

	cl_context_properties cps[3] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };

	/////////////////////////////////////////////////////////////////
	// Create an OpenCL context
	/////////////////////////////////////////////////////////////////
	context = clCreateContextFromType(cps, type, NULL, NULL, &status);
	SafeCall(status, "Error: Creating Context. (clCreateContextFromType)");

	/* First, get the size of device list data */
	SafeCall(clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &deviceListSize), "Error: Getting Context Info (device list size, clGetContextInfo)");

	/////////////////////////////////////////////////////////////////
	// Detect OpenCL devices
	/////////////////////////////////////////////////////////////////
	devices = new cl_device_id[deviceListSize/sizeof(cl_device_id)];
	SafeCall(!devices, "Error: No devices found.");

	/* Now, get the device list data */
	SafeCall(clGetContextInfo(context, CL_CONTEXT_DEVICES, deviceListSize, devices, NULL), "Error: Getting Context Info (device list, clGetContextInfo)");

	char platformVendor[1024];
	SafeCall(clGetPlatformInfo(platform, CL_PLATFORM_VENDOR, sizeof(platformVendor), platformVendor, NULL), "clGetPlatformInfo failed");
	cout << "Selected Platform Vendor : " << platformVendor << endl;

	// Get number of devices available 
	cl_uint deviceCount = 0;
	SafeCall(clGetDeviceIDs(platform, type, 0, NULL, &deviceCount), "clGetDeviceIDs failed");

	cl_device_id* deviceIds = (cl_device_id*)malloc(sizeof(cl_device_id) * deviceCount);
	SafeCall(!deviceIds, "Failed to allocate memory(deviceIds)");

	// Get device ids
	SafeCall(clGetDeviceIDs(platform, type, deviceCount, deviceIds, NULL), "clGetDeviceIDs failed");

	cout << "Available devices are: " << endl;
	// Print device index and device names
	for(cl_uint i = 0; i < deviceCount; ++i)
	{
		char deviceName[1024];
		SafeCall(clGetDeviceInfo(deviceIds[i], CL_DEVICE_NAME, sizeof(deviceName), deviceName, NULL), "clGetDeviceInfo failed");
		cout << "Device " << i << " : " << deviceName <<" Device ID is "<<deviceIds[i]<< endl;
	}
	free(deviceIds);
	/////////////////////////////////////////////////////////////////
	// Create an OpenCL command queue
	/////////////////////////////////////////////////////////////////
	cout << "Running on Device 0..." << endl;
	commandQueue = clCreateCommandQueue(context, devices[0], CL_QUEUE_PROFILING_ENABLE, &status);
	SafeCall(status, "Creating Command Queue. (clCreateCommandQueue)");

	return 0;
}
int COpenCLTemplate::AllocateMemoryGPU()
{
	cl_int status;

	cl_matrix_A = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, sizeof(PRECISION)*rows_matrix_A * colums_matrix_A, matrix_A, &status);
	SafeCall(status, "Error: clCreateBuffer() cannot create input buffer");
	cl_matrix_B_transposer = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, sizeof(PRECISION)*rows_matrix_B * colums_matrix_B, matrix_B_transpose, &status);
	SafeCall(status, "Error: clCreateBuffer() cannot create input buffer");
	cl_matrix_C = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR, sizeof(PRECISION)*rows_matrix_A * colums_matrix_B, matrix_C, &status);
	SafeCall(status, "Error: clCreateBuffer() cannot create input buffer");
	return 0;
}

int COpenCLTemplate::InitialiseCLKernelsGPU()
{
	int status;
	/////////////////////////////////////////////////////////////////
	// Load CL file, build CL program object, create CL kernel object
	/////////////////////////////////////////////////////////////////
	const char *filename = "OpenCLTemplate_Kernels.cl";
	string sourceStr = convertToString(filename);
	const char *source = sourceStr.c_str();
	size_t sourceSize[] = {strlen(source)};

	program = clCreateProgramWithSource( context, 1, &source, sourceSize, &status);
	SafeCall(status, "Error: Loading Binary into cl_program (clCreateProgramWithBinary)\n");

	// Create a cl program executable for all the devices specified.
	status = clBuildProgram(program, 1, devices, NULL, NULL, NULL);
	if(status == CL_BUILD_PROGRAM_FAILURE)
	{
		cl_int logStatus;
		char *buildLog = NULL;
		size_t buildLogSize = 0;
		logStatus = clGetProgramBuildInfo (program, devices[0], CL_PROGRAM_BUILD_LOG, buildLogSize, buildLog, &buildLogSize);
		SafeCall(logStatus, "clGetProgramBuildInfo failed.");
		buildLog = new char[buildLogSize];
		SafeCall(!buildLog, "Failed to allocate host memory. (buildLog)");
		memset(buildLog, 0, buildLogSize);
		logStatus = clGetProgramBuildInfo (program, devices[0], CL_PROGRAM_BUILD_LOG, buildLogSize, buildLog, NULL);
		if (logStatus != CL_SUCCESS)
		{
			cout << "clGetProgramBuildInfo failed." << endl;
			free(buildLog);
			return -1;
		}
		// Displaying build log in case of errors.
		cout << " \n\t\t\tBUILD LOG\n";
		cout << " ************************************************\n";
		cout << buildLog << endl;
		cout << " ************************************************\n";
		delete []buildLog;
	}

	// Attach kernel objects to respective kernel functions.
	kernel = clCreateKernel(program, "OpenCLTemplateKernel", &status);
	SafeCall(status, "Error: Creating Kernel from program. (clCreateKernel)");
/*	cout<<rows_matrix_A<<endl;
	cout<<colums_matrix_A<<endl;
	cout<<rows_matrix_B<<endl;
	cout<<start_thread<<endl;
*/	// ====== Set appropriate arguments to the kernel ======
	SafeCall(clSetKernelArg(kernel, 0, sizeof(cl_mem), (void*)&cl_matrix_A), "Error: Setting kernel argument 'matrix A'");
	SafeCall(clSetKernelArg(kernel, 1, sizeof(cl_mem), (void*)&cl_matrix_B_transposer), "Error: Setting kernel argument 'matrix B'");
	SafeCall(clSetKernelArg(kernel, 2, sizeof(cl_mem), (void*)&cl_matrix_C), "Error: Setting kernel argument 'matrix C'");
	SafeCall(clSetKernelArg(kernel, 3, sizeof(int), (void*)&rows_matrix_A), "Error: Setting kernel argument 'rows_matrix A'");
	SafeCall(clSetKernelArg(kernel, 4, sizeof(int), (void*)&colums_matrix_A), "Error: Setting kernel argument 'colums_matrix A'");
	SafeCall(clSetKernelArg(kernel, 5, sizeof(int), (void*)&rows_matrix_B), "Error: Setting kernel argument 'rows_matrix B'");
	SafeCall(clSetKernelArg(kernel, 6, sizeof(int), (void*)&colums_matrix_B), "Error: Setting kernel argument 'colums_matrix B'");
	SafeCall(clSetKernelArg(kernel, 7, sizeof(int), (void*)&start_thread), "Error: Setting kernel argument 'colums_matrix B'");
	return 0;
}
int COpenCLTemplate::RunCLKernels()
{
	cl_int status;
	cl_uint maxDims;
	cl_event events[2];
	size_t globalThreads[2];
	size_t localThreads[2];
	size_t maxWorkGroupSize;
	size_t maxWorkItemSizes[3];

	// Query device capabilities. Maximum work item dimensions and the maximmum work item sizes
	SafeCall(clGetDeviceInfo(devices[0], CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(size_t), (void*)&maxWorkGroupSize, NULL), "Error: Getting Device Info. (clGetDeviceInfo)");
	SafeCall(clGetDeviceInfo(devices[0], CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(cl_uint), (void*)&maxDims, NULL), "Error: Getting Device Info. (clGetDeviceInfo)");
	SafeCall(clGetDeviceInfo(devices[0], CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(size_t)*maxDims, (void*)maxWorkItemSizes, NULL), "Error: Getting Device Info. (clGetDeviceInfo)");
	int NumerOfCLThreads0 =(int)(log((double)rows_matrix_A)/log(2.0));
	NumerOfCLThreads0 = pow(2,NumerOfCLThreads0+0.0);
	int NumerOfCLThreads1 =(int)(log((double)colums_matrix_B)/log(2.0))+1;
	NumerOfCLThreads1 = pow(2,NumerOfCLThreads1+0.0);
	cout<<NumerOfCLThreads1<<endl;
	localThreads[0]  = 16;
	localThreads[1]  = 16;
	globalThreads[0] = NumerOfCLThreads0;
	globalThreads[1] = NumerOfCLThreads1;
	
	cout << "Max dimensions: " << maxDims << endl;
	cout << "Device maxWorkGroupSize = " << maxWorkGroupSize << endl;
	cout << "Device maxWorkItemSizes = " << maxWorkItemSizes[0] << endl;
	if(localThreads[0] > maxWorkGroupSize || localThreads[0] > maxWorkItemSizes[0])
	{
		cout<<"Unsupported: Device does not support requested number of work items." << endl;
		return 1;
	}

	// Kernel timing variables.
	cl_ulong startTime, endTime;
	cl_ulong kernelExecTimeNs;
	cl_ulong kernelExecTimeNsT = 0;

	cout << "Launching CL Kernel..." << endl;
	cout << "Global threads: " << globalThreads[0] << "x" << globalThreads[1] << endl;
	cout << "Local threads: " << localThreads[0] << "x" << localThreads[1] << endl;

	// Enqueue a kernel call.
	status = clEnqueueNDRangeKernel(commandQueue, kernel, 2, NULL, globalThreads, localThreads, 0, NULL, &events[0]);
	if(status != CL_SUCCESS) 
	{ 
		cout << "Error: Enqueueing kernel onto command queue (clEnqueueNDRangeKernel)" << endl;
		if ( status == CL_INVALID_COMMAND_QUEUE ) cout << "CL_INVALID_COMMAND_QUEUE." << endl;
		if ( status == CL_INVALID_PROGRAM_EXECUTABLE ) cout << "CL_INVALID_PROGRAM_EXECUTABLE." << endl;
		if ( status == CL_INVALID_KERNEL ) cout << "CL_INVALID_KERNEL." << endl;
		if ( status == CL_INVALID_WORK_DIMENSION ) cout << "CL_INVALID_WORK_DIMENSION." << endl;
		if ( status == CL_INVALID_CONTEXT ) cout << "CL_INVALID_CONTEXT." << endl;
		if ( status == CL_INVALID_KERNEL_ARGS ) cout << "CL_INVALID_KERNEL_ARGS." << endl;
		if ( status == CL_INVALID_WORK_GROUP_SIZE ) cout << "CL_INVALID_WORK_GROUP_SIZE." << endl;
		if ( status == CL_INVALID_WORK_ITEM_SIZE ) cout << "CL_INVALID_WORK_ITEM_SIZE." << endl;
		if ( status == CL_INVALID_GLOBAL_OFFSET ) cout << "CL_INVALID_GLOBAL_OFFSET." << endl;
		return 1;
	}

	// Wait for the kernel call to finish execution.
	SafeCall(clWaitForEvents(1, &events[0]), "Error: Waiting for kernel run to finish. (clWaitForEvents)");

	clGetEventProfilingInfo(events[0], CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &startTime, NULL);
	clGetEventProfilingInfo(events[0], CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &endTime, NULL);
//	kernelExecTimeNs = (cl_ulong)(1e-3*(endTime-startTime));
//	kernelExecTimeNsT = kernelExecTimeNsT + kernelExecTimeNs;

	cout << "Kernel run complete!" << endl;
//	cout << "Kernel execution time = " << kernelExecTimeNsT/1e6 << "sec (" << kernelExecTimeNsT/1e3 << "ms or " << kernelExecTimeNsT << "us)" << endl;
	SafeCall(clReleaseEvent(events[0]), "Error: Release event object. (clReleaseEvent)\n");

	// Copying output array data back to CPU.
	SafeCall(status = clEnqueueReadBuffer(commandQueue, /*Source: GPU*/cl_matrix_C, CL_TRUE, 0,  /*Size of data in bytes*/sizeof(PRECISION)*rows_matrix_A*colums_matrix_B, /*Destination: CPU*/matrix_C, 0, NULL, &events[1]),"Error: clEnqueueReadBuffer Failed for output array.");
	SafeCall(clWaitForEvents(1, &events[1]), "Error: Waiting for output array to be copied back to CPU. (clWaitForEvents)");
	SafeCall(clReleaseEvent(events[1]), "Error: Release event object. (clReleaseEvent)\n");

	cout << "Output array is: " << endl;
	//SafeCall(DisplayArray(Width, output), "Error: Displaying output array.");
	
	return 0;
}
int COpenCLTemplate::CompleteRun()
{

//	SafeCall(AllocateMemoryCPU(), "Error: Allocating memory on CPU.");
	SafeCall(InitialiseCPU(), "Error: Initialising data on CPU.");
	MatrixTranspose();
	cout<<"check 1"<<endl;
	SafeCall(InitialiseCL(), "Error: Initialiasing CL.");
	cout<<"check 2"<<endl;
	SafeCall(AllocateMemoryGPU(), "Error: Allocating memory on GPU.");
	cout<<"check 3"<<endl;
	SafeCall(InitialiseCLKernelsGPU(), "Error: Copying data from CPU to GPU.");
	cout<<"check 4"<<endl;
	SafeCall(RunCLKernels(), "Error: Running kernels (GPU).");
//	MatrixStore();
	cout<<"check 5"<<endl;
	SafeCall(CleanupCPU(), "Error: Cleaning up CPU.");
	SafeCall(CleanupCL(), "Error: Cleaning up CL.");
	SafeCall(CleanupGPU(), "Error: Cleaning up GPU.");
	cout<<"check 6"<<endl;
	return 0;
}
// Converts contents of a file into a string. From OPENCL examples.
string COpenCLTemplate::convertToString(const char *filename)
{
	size_t size;
	char*  str;
	string s;
	fstream f(filename, (fstream::in | fstream::binary));

	if(f.is_open())
	{
		size_t fileSize;
		f.seekg(0, fstream::end);
		size = fileSize = (size_t)f.tellg();
		f.seekg(0, fstream::beg);

		str = new char[size+1];
		if(!str)
		{
			f.close();
			return NULL;
		}

		f.read(str, fileSize);
		f.close();
		str[size] = '\0';

		s = str;
		delete[] str;
		return s;
	}
	else
	{
		cout << "\nFile containg the kernel code(\".cl\") not found. Please copy the required file in the folder containg the executable." << endl;
		exit(1);
	}
	return NULL;
}

// Display array.
int COpenCLTemplate::DisplayArray(const unsigned int Size, PRECISION* Array)
{
	/*for (unsigned int i=0; i<Size; i++)
		cout << Array[i] << " ";
	cout << endl;
	*/
	return 0;
}

// Timing.
void COpenCLTemplate::StartTimer()
{
	if (tPaused == true)
	{
		tStart = GetTimeus64();
		tPaused = false;
	}
}
void COpenCLTemplate::StopTimer()
{
	if (tPaused == false)
	{
		tEnd = GetTimeus64();
		tDelta += tEnd - tStart;
		tStart = tEnd;
		tPaused = true;
	}
}
void COpenCLTemplate::ResetTimer()
{
	if (tPaused == true)
		tStart = tEnd;
	else
		tStart = GetTimeus64();

	tDelta = 0UL;
}
double COpenCLTemplate::GetElapsedTime()
{
	if (tPaused == false)
		tEnd = GetTimeus64();

	return ((double)(tEnd-tStart+tDelta))/(1000000.);
}
int SafeCall(cl_int Status, const char *Error)
{
	if (Status != 0)
	{
		if (Error!=NULL) cout << Error << endl;
		exit(Status);
	}
	return Status;
}
template<typename T> void DeleteArray(T *&ptr)
{
	if (ptr != NULL)
	{
		delete[] ptr;
		ptr = NULL;
	}
}
int COpenCLTemplate::CleanupCPU()
{
	DeleteArray(matrix_A);
	DeleteArray(matrix_B);
//	DeleteArray(matrix_C);

	return 0;
}
int COpenCLTemplate::CleanupCL()
{
	SafeCall(clReleaseKernel(kernel), "Error: In clReleaseKernel");
	SafeCall(clReleaseProgram(program), "Error: In clReleaseProgram");
	SafeCall(clReleaseCommandQueue(commandQueue), "Error: In clReleaseCommandQueue");
	SafeCall(clReleaseContext(context), "Error: In clReleaseContext");

	return 0;
}
int COpenCLTemplate::CleanupGPU()
{
	SafeCall(clReleaseMemObject(cl_matrix_A), "Error: clReleaseMemObject() cannot release input memory buffer");
	SafeCall(clReleaseMemObject(cl_matrix_B_transposer), "Error: clReleaseMemObject() cannot release output memory buffer");
	SafeCall(clReleaseMemObject(cl_matrix_C), "Error: clReleaseMemObject() cannot release output memory buffer");

	return 0;
}
COpenCLTemplate::~COpenCLTemplate ()
{
//	DeleteArray(matrix_A);
//	DeleteArray(matrix_B);
//	DeleteArray(matrix_C);
}
int COpenCLTemplate::MatrixStore()
{

		ofstream matrix_A_write("matrix_A.txt");
		for (int i = 0; i < rows_matrix_A; i++)
		{
			for (int j = 0; j < colums_matrix_A; j++)
				matrix_A_write << matrix_A[j+i*colums_matrix_A]<< "   ";
			matrix_A_write << endl;
		}
		matrix_A_write.close();
		ofstream matrix_B_write("matrix_B.txt");
		for (int i = 0; i < rows_matrix_B; i++)
		{
			for (int j = 0; j < colums_matrix_B; j++)
				matrix_B_write << matrix_B[i*colums_matrix_B+j]<< "   ";
			matrix_B_write << endl;
		}
		matrix_B_write.close();
		ofstream matrix_C_write("matrix_C.txt");
		for (int i = 0; i < rows_matrix_A; i++)
		{
			for (int j = 0; j < colums_matrix_B; j++)
				matrix_C_write << matrix_C[i*colums_matrix_B+j]<< "   ";
			matrix_C_write << endl;
		}
		matrix_C_write.close();
	return 0;
}
/*
size_t COpenCLTemplate:: shrRoundUp(int group_size, int global_size)
{
	int r = global_size % group_size;
	if(r == 0)
	{
		return global_size;
	}
	 else
	{
		return global_size + group_size - r;
	}
}*/